A Little Context
Before we get started, this tutorial is deliberately organized in an inconvenient fashion. Why? My target reader is someone in a class I'm teaching (the material is open-source but still targeted). I do not want the student to be able to quickly just copy the whole thing and get it to work without having to put forth some effort. In a classroom situation, I have all of the source code available if I need to help a student get up to speed.
We'll start with a stripped down version of the requirements. This first test suite handles the following test cases:
Create a Patron
Remove a Patron
Update a Patron
Attempt to find Patron that does not exist.
Notice that this suite of tests is for Creating, Reading, Updating and Deleting (CRUD) Patrons.
Assuming you've done Tutorial 2, much of the boilerplate code is going to look the same. First let's write a unit test for each of these test cases: Create a Patron
@Test
publicvoid createAPatron(){final Patron p = createAPatronImpl();final Patron found = dao.retrieve(p.getId());
assertNotNull(found);}private Patron createAPatronImpl(){final Address a = new Address("5080 Spectrum Drive", "Suite 700 West",
"Addison", "TX", "75001");return dao.createPatron("Brett", "Schuchert", "972-555-1212", a);}
This test first creates a patron using a private utility method. This method exists because it is used later in other unit tests.
Looking at the test, it uses an attribute called dao. This is a Data Access Object (which we'll later convert to a stateless Session Bean). This Data Access Object will be responsible for retrieving, creating and removing Patrons. Remove a Patron
@Test
publicvoid removeAPatron(){final Patron p = createAPatronImpl();
dao.removePatron(p.getId());final Patron found = dao.retrieve(p.getId());
assertNull(found);}
This test uses the utility method to create a patron. It then removes it and makes sure that when we try to retrieve it that the Patron no longer exists.
Update a Patron
@Test
publicvoid updateAPatron(){final Patron p = createAPatronImpl();finalString originalPhoneNumber = p.getPhoneNumber();
p.setPhoneNumber(NEW_PN);
dao.update(p);final Patron found = dao.retrieve(p.getId());
assertNotNull(found);
assertFalse(NEW_PN.equals(originalPhoneNumber));
assertEquals(NEW_PN, p.getPhoneNumber());}
We create a patron then update it.
Attempt to find Patron that does not exist
@Test
publicvoid tryToFindPatronThatDoesNotExist(){finalLong id = -18128129831298l;final Patron p = dao.retrieve(id);
assertNull(p);}
Verify that when we try to find a patron that's not found, we get back null.
Supporting Code
We have several veterans returning from previous tutorials. And here they are:
PatronDaoTest
First the imports and the attributes. Note that this is a complete class that will compile. It just doesn't do anything yet.
Initialization of the Logger
This is our 1-time initialization of the logging system.
@BeforeClass
publicstaticvoid initLogger(){// Produce minimal output.
BasicConfigurator.configure();// Comment this line to see a lot of initialization// status logging.Logger.getLogger("org").setLevel(Level.ERROR);}
Getting EMF and EM
Now before each unit test we'll look up the entity manager factory, create a dao, create an entity manager and pass it into a DAO and finally start a transaction.
@Before
publicvoid initEmfAndEm(){
emf = Persistence.createEntityManagerFactory("lis");
dao = new PatronDao();
dao.setEm(emf.createEntityManager());
dao.getEm().getTransaction().begin();}
Clean up after each test
After each test we'll rollback the transaction we created in the pre-test initialization. We'll then close both the entity manager and entity manager factory. This keeps our tests isolated.
We need to create entities. These entities are a bit more well-specified that what you've seen in the previous tutorials. In most cases I believe the extra information is intuitive. Where it is not, I'll try to point out what is going on.
Address.java
packageentity;importjavax.persistence.Column;importjavax.persistence.Entity;importjavax.persistence.GeneratedValue;importjavax.persistence.Id;
@Entity// The next class is a JPA entitypublicclass Address {
@Id // The next attribute (in this case) or method is a key field
@GeneratedValue // the key is auto-generatedprivateLong id;
@Column(length = 50)// the next column is 50 characters in sizeprivateString streetAddress1;
@Column(length = 50)privateString streetAddress2;
@Column(length = 20)privateString city;
@Column(length = 2)privateString state;
@Column(length = 9)privateString zip;public Address(){}public Address(finalString sa1, finalString sa2, finalString city,
finalString state, finalString zip){
setStreetAddress1(sa1);
setStreetAddress2(sa2);
setCity(city);
setState(state);
setZip(zip);}publicString getCity(){return city;}publicvoid setCity(finalString city){this.city = city;}publicString getState(){return state;}publicvoid setState(finalString state){this.state = state;}publicString getStreetAddress1(){return streetAddress1;}publicvoid setStreetAddress1(finalString streetAddress1){this.streetAddress1 = streetAddress1;}publicString getStreetAddress2(){return streetAddress2;}publicvoid setStreetAddress2(finalString streetAddress2){this.streetAddress2 = streetAddress2;}publicString getZip(){return zip;}publicvoid setZip(finalString zip){this.zip = zip;}publicLong getId(){return id;}publicvoid setId(Long id){this.id = id;}}
Patron.java
packageentity;importjavax.persistence.CascadeType;importjavax.persistence.Column;importjavax.persistence.Entity;importjavax.persistence.GeneratedValue;importjavax.persistence.Id;importjavax.persistence.OneToOne;
@Entity// the next class is a JPA entitypublicclass Patron {
@Id
@GeneratedValue
privateLong id;// when in the database, this field cannot be null, as an object in memory// it can
@Column(length = 20, nullable = false)privateString firstName;
@Column(length = 30, nullable = false)privateString lastName;
@Column(length = 11, nullable = false)privateString phoneNumber;// This next field refers to an object that is stored in another table.// All updates are cascaded. So if you persist me, my address, which is in// another table, will be persisted automatically. Updates and removes are// also cascaded automatically.
@OneToOne(cascade = CascadeType.ALL)private Address address;public Patron(finalString fName, finalString lName, finalString phone,
final Address a){
setFirstName(fName);
setLastName(lName);
setPhoneNumber(phone);
setAddress(a);}public Address getAddress(){return address;}publicvoid setAddress(Address address){this.address = address;}publicString getFirstName(){return firstName;}publicvoid setFirstName(String firstName){this.firstName = firstName;}publicLong getId(){return id;}publicvoid setId(Long id){this.id = id;}publicString getLastName(){return lastName;}publicvoid setLastName(String lastName){this.lastName = lastName;}publicString getPhoneNumber(){return phoneNumber;}publicvoid setPhoneNumber(String phoneNumber){this.phoneNumber = phoneNumber;}}
Finally, the Data Access Object
The DAO has the following four methods:
createPatron
retrieve
removePatron
update
We'll look at each of these, although none of this will be new if you've looked at the first tutorial. createPatron
Given the information to create a patron, instantiate one and then persiste it. Note that this is written in a style that will natually fit into a Session Bean.
public Patron createPatron(finalString fname, finalString lname,
finalString phoneNumber, final Address a){final Patron p = new Patron(fname, lname, phoneNumber, a);
getEm().persist(p);return p;}
retrieve
This uses the find method built into the entity manager. It returns null if not found. It first sees if the object is in the first-level cache. If not, it retrieves it from the database.
public Patron retrieve(finalLong id){return getEm().find(Patron.class, id);}
removePatron
To remove an object we have to find it first. You do not provide a class and a key. So we first retrieve it (it might already be in the cache so this may not involve a database hit. We then issue the remove of the object.
publicvoid removePatron(finalLong id){final Patron p = retrieve(id);if(p != null){
getEm().remove(p);}}
update
Update uses the merge method to get its work done. Note that it returns what is returned from merge. Why? The provided patron could be detached (retrieve during another transaction or from a different instance of an entity manager. If this is the case, then the object will not be put into the cache (and therefore become managed). Instead, a new instance is created and the contents of the paramter is copied into the new, managed instance. That new managed instance is returned. If the provided patron is managed, then there's actually not need to even call this method because any changes made to the patron will be reflected in the patron after the transaction is closed.
public Patron update(final Patron p){return getEm().merge(p);}
The rest of the class
Here are the imports, attributes and getters/setters.
The Unit Tests
A Little ContextBefore we get started, this tutorial is deliberately organized in an inconvenient fashion. Why? My target reader is someone in a class I'm teaching (the material is open-source but still targeted). I do not want the student to be able to quickly just copy the whole thing and get it to work without having to put forth some effort. In a classroom situation, I have all of the source code available if I need to help a student get up to speed.
We'll start with a stripped down version of the requirements. This first test suite handles the following test cases:
Notice that this suite of tests is for Creating, Reading, Updating and Deleting (CRUD) Patrons.
Assuming you've done Tutorial 2, much of the boilerplate code is going to look the same. First let's write a unit test for each of these test cases:
Create a Patron
@Test public void createAPatron() { final Patron p = createAPatronImpl(); final Patron found = dao.retrieve(p.getId()); assertNotNull(found); } private Patron createAPatronImpl() { final Address a = new Address("5080 Spectrum Drive", "Suite 700 West", "Addison", "TX", "75001"); return dao.createPatron("Brett", "Schuchert", "972-555-1212", a); }This test first creates a patron using a private utility method. This method exists because it is used later in other unit tests.
Looking at the test, it uses an attribute called dao. This is a Data Access Object (which we'll later convert to a stateless Session Bean). This Data Access Object will be responsible for retrieving, creating and removing Patrons.
Remove a Patron
@Test public void removeAPatron() { final Patron p = createAPatronImpl(); dao.removePatron(p.getId()); final Patron found = dao.retrieve(p.getId()); assertNull(found); }This test uses the utility method to create a patron. It then removes it and makes sure that when we try to retrieve it that the Patron no longer exists.
Update a Patron
@Test public void updateAPatron() { final Patron p = createAPatronImpl(); final String originalPhoneNumber = p.getPhoneNumber(); p.setPhoneNumber(NEW_PN); dao.update(p); final Patron found = dao.retrieve(p.getId()); assertNotNull(found); assertFalse(NEW_PN.equals(originalPhoneNumber)); assertEquals(NEW_PN, p.getPhoneNumber()); }We create a patron then update it.Attempt to find Patron that does not exist
@Test public void tryToFindPatronThatDoesNotExist() { final Long id = -18128129831298l; final Patron p = dao.retrieve(id); assertNull(p); }Verify that when we try to find a patron that's not found, we get back null.
Supporting Code
We have several veterans returning from previous tutorials. And here they are:PatronDaoTest
First the imports and the attributes. Note that this is a complete class that will compile. It just doesn't do anything yet.
Initialization of the Logger
This is our 1-time initialization of the logging system.
@BeforeClass public static void initLogger() { // Produce minimal output. BasicConfigurator.configure(); // Comment this line to see a lot of initialization // status logging. Logger.getLogger("org").setLevel(Level.ERROR); }Getting EMF and EM
Now before each unit test we'll look up the entity manager factory, create a dao, create an entity manager and pass it into a DAO and finally start a transaction.
@Before public void initEmfAndEm() { emf = Persistence.createEntityManagerFactory("lis"); dao = new PatronDao(); dao.setEm(emf.createEntityManager()); dao.getEm().getTransaction().begin(); }Clean up after each test
After each test we'll rollback the transaction we created in the pre-test initialization. We'll then close both the entity manager and entity manager factory. This keeps our tests isolated.
@After public void closeEmAndEmf() { dao.getEm().getTransaction().rollback(); dao.getEm().close(); emf.close(); }The Entities
We need to create entities. These entities are a bit more well-specified that what you've seen in the previous tutorials. In most cases I believe the extra information is intuitive. Where it is not, I'll try to point out what is going on.Address.java
Patron.java
Finally, the Data Access Object
The DAO has the following four methods:
We'll look at each of these, although none of this will be new if you've looked at the first tutorial.
createPatron
Given the information to create a patron, instantiate one and then persiste it. Note that this is written in a style that will natually fit into a Session Bean.
retrieve
This uses the find method built into the entity manager. It returns null if not found. It first sees if the object is in the first-level cache. If not, it retrieves it from the database.
removePatron
To remove an object we have to find it first. You do not provide a class and a key. So we first retrieve it (it might already be in the cache so this may not involve a database hit. We then issue the remove of the object.
update
Update uses the merge method to get its work done. Note that it returns what is returned from merge. Why? The provided patron could be detached (retrieve during another transaction or from a different instance of an entity manager. If this is the case, then the object will not be put into the cache (and therefore become managed). Instead, a new instance is created and the contents of the paramter is copied into the new, managed instance. That new managed instance is returned. If the provided patron is managed, then there's actually not need to even call this method because any changes made to the patron will be reflected in the patron after the transaction is closed.
The rest of the class
Here are the imports, attributes and getters/setters.